package br.com.ordersys.framework.db;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Statement;

import javax.naming.InitialContext;
import javax.sql.DataSource;

import br.com.ordersys.framework.db.config.IMetadataProvider;
import br.com.ordersys.framework.db.metadata.TableMetadata;
import br.com.ordersys.framework.utils.ExceptionUtils;

public class DataSourceFactory {
	private static DataSourceFactory singleton;
	private InitialContext jndiContext;

	private DataSourceFactory() {
		try {
			jndiContext = new InitialContext();
		} catch (Exception e) {
			ExceptionUtils.throwAsUnchecked(e);
		}
	}

	public static synchronized DataSourceFactory getSingleton() {
		if (singleton == null) {
			singleton = new DataSourceFactory();
		}

		return singleton;
	}

	private void applySchemaOnConnection(Connection c, String schema) throws Exception {
		DatabaseMetaData dbmd = c.getMetaData();
		String dbName = dbmd.getDatabaseProductName().toLowerCase();

		String cmd = null;

		if (dbName.indexOf("oracle") > -1) {
			cmd = "SET SCHEMA = " + schema;
		} else if (dbName.indexOf("sqlserver") > -1 || dbName.indexOf("sql server") > -1) {
			cmd = "USE " + schema;
		} else if (dbName.indexOf("mysql") > -1) {
			cmd = "USE " + schema;
		}

		Statement stm = c.createStatement();
		stm.executeUpdate(cmd);
		stm.close();

	}

	public String getTenantIdentifier() {
		String tenantID = DBSessionManager.getCurrentSession().getTenantToUse();
		
		IMetadataProvider metadataProvider = DBCore.getInstance().getMetadataProvider();

		if(tenantID == null){
			tenantID = metadataProvider.getCurrentTenantId();
		}else if(tenantID.equals("##main##")){
			tenantID = metadataProvider.getMainTenantId();
		}
		
		return tenantID;
	}

	public DataSource getTenantAwareDataSource() {

		InvocationHandler hnd = new InvocationHandler() {
			DataSource delegateDS;

			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				IMetadataProvider metadataProvider = DBCore.getInstance().getMetadataProvider();
				
				if (delegateDS == null) {
					delegateDS = (DataSource) jndiContext.lookup("java:/" + metadataProvider.getTenantAwareDataSourceName());
				}

				Object result = method.invoke(delegateDS, args);

				if (result instanceof Connection) {
					String tenantID = null;

					if (metadataProvider.getTenancyStrategy() == IMetadataProvider.TENANCY_SCHEMA_STRATEGY) {
						tenantID = DBSessionManager.getCurrentSession().getTenantToUse();
						
						if(tenantID == null){
							tenantID = metadataProvider.getCurrentTenantId();
						}else if(tenantID.equals("##main##")){
							tenantID = metadataProvider.getMainTenantId();
						}

						applySchemaOnConnection((Connection) result, tenantID);
					}

					result = JDBCConnectionProxy.wrapProxy((Connection) result, tenantID);
				}

				return result;
			}
		};

		return (DataSource) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { DataSource.class }, hnd);
	}

	public DataSource getMainDataSource() {

		InvocationHandler hnd = new InvocationHandler() {
			DataSource delegateDS;

			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if (delegateDS == null) {
					delegateDS = (DataSource) jndiContext.lookup("java:/" + DBCore.getInstance().getMetadataProvider().getMainDataSourceName());
				}

				Object result = method.invoke(delegateDS, args);

				if (result instanceof Connection) {
					IMetadataProvider metadataProvider = DBCore.getInstance().getMetadataProvider();

					applySchemaOnConnection((Connection) result, metadataProvider.getMainTenantId());

					result = JDBCConnectionProxy.wrapProxy((Connection) result, null);
				}

				return result;
			}
		};

		return (DataSource) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { DataSource.class }, hnd);
	}

	public TableMetadata resolveTable(String name) {
		return DBCore.getInstance().getMetadataProvider().loadTableMetadata(name);
	}

}
